package com.lagou.edu.utils;

import com.lagou.edu.factory.annotation.CustomRepository;
import com.lagou.edu.factory.annotation.CustomService;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.annotation.Annotation;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * @author czq
 * @date 2020/7/7 23:33
 *
 * 注解工具类
 */
public class AnnotationUtil {

    public static List<Class<?>> getFromPackage(String packageName) throws Exception {

        Class<? extends Annotation>[] annotationArr = new Class[]{CustomService.class, CustomRepository.class};
        List<Class<?>> classList = new ArrayList<>();
        String packageDirName = packageName.replace('.', '/');
        Enumeration<URL> dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);

        while (dirs.hasMoreElements()) {
            URL url = dirs.nextElement();
            String protocol = url.getProtocol();

            if ("file".equals(protocol) ) {
                String filePath = URLDecoder.decode(url.getFile(), "UTF-8").substring(1);
                getClassesFromFilePath(packageName, filePath, classList, annotationArr);
            } else if ("jar".equals(protocol)) {
                JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile();
                List<Class<?>> alClassList = new ArrayList<>();
                findClassesByJar(packageName, jar, alClassList);
                getClassesFromAllClasses(alClassList, annotationArr, classList);
            }
        }

        return classList;
    }

    private static void findClassesByJar(String pkgName, JarFile jar, List<Class<?>> classes) throws Exception {
        String pkgDir = pkgName.replace(".", "/");
        Enumeration<JarEntry> entry = jar.entries();

        while (entry.hasMoreElements()) {
            JarEntry jarEntry = entry.nextElement();
            String name = jarEntry.getName();
            if (name.charAt(0) == '/') {
                name = name.substring(1);
            }
            if (jarEntry.isDirectory() || !name.startsWith(pkgDir) || !name.endsWith(".class")) {
                continue;
            }

            String className = name.substring(0, name.length() - 6);
            Class<?> tempClass = loadClass(className.replace("/", "."));
            if (tempClass != null) {
                classes.add(tempClass);
            }
        }
    }

    private static Class<?> loadClass(String fullClsName) throws Exception {
        return Thread.currentThread().getContextClassLoader().loadClass(fullClsName);
    }

    private static void getClassesFromFilePath(String packageName, String filePath, List<Class<?>> classList, Class<? extends Annotation>[] annotationArr) throws Exception {
        Path dir = Paths.get(filePath);

        try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
            for(Path path : stream) {
                String fileName = String.valueOf(path.getFileName());
                boolean isDir = Files.isDirectory(path);
                if(isDir) {
                    getClassesFromFilePath(packageName + "." + fileName , path.toString(), classList, annotationArr);
                }
                else  {
                    String className = fileName.substring(0, fileName.length() - 6);
                    String fullClassPath = packageName + "." + className;
                    Class<?> classes = Thread.currentThread().getContextClassLoader().loadClass(fullClassPath);

                    for (int i = 0; i < annotationArr.length; i++) {
                        if (null != classes && null != classes.getAnnotation(annotationArr[i])) {
                            classList.add(classes);
                        }
                    }
                }
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void getClassesFromAllClasses(List<Class<?>> inClassList, Class<? extends Annotation>[] annotationArr, List<Class<?>> outClassList) {
        for(Class<?> myClasss : inClassList) {
            for (int i = 0; i < annotationArr.length; i++) {
                if (null != myClasss && null != myClasss.getAnnotation(annotationArr[i])) {
                    outClassList.add(myClasss);
                }
            }
        }
    }
}
